by Keith McIntyre
In This Chapter
When writing programs, you must be concerned not only with the normal operation of the program but also the kind of events that happen in the real world: unexpected user inputs, low memory conditions, disk drive errors, unavailable network resources, unavailable databases, and so on. One way a programmer can handle such real-world eventualities is to use exceptions.
The concept of handling exceptions is not foreign to software development. The ubiquitous if-then-else construct is often used to handle detection and processing of special cases such as bad user input or the inability to open a disk file. The code snippet in Listing 22.1 shows an example of making such runtime decisions.
Listing 22.1 Example of Using Nested If-Then-Else Statements
void NestedIfExample()
{
// open an input file, and output file, and a log file
FILE * fpIn;
FILE * fpOut;
FILE * fpLog;
fpIn = fopen( c:\\temp\\in.txt, r ); if ( fpIn )
{
fpOut = fopen( c:\\temp\\out.txt, w+ );
if ( fpOut )
{
fpLog = fopen( c:\\temp\\log.out, a+ );
if ( fpOut )
{
// do some processing on fpIn producing fpOut
// while logging to fpLog
// close all the files
fclose( fpIn );
fclose( fpOut );
fclose( fpLog );
}
else
{
// error opening log file - close in and out
fclose( fpIn );
fclose( fpOut );
printf( error opening log file\n);
}
}
else
{
// error opening out file - close the in file
fclose( fpIn );
printf( error opening output file\n);
}
}
else
{
printf(error opening input file\n);
}
}
The code in Listing 22.1 uses nested conditional statements to detect and handle several errors that might occur during the normal processing of a simple function. If the logic becomes complex, the number of possible if-then-else statements, and consequently the level of nesting, becomes excessive and unruly.
Another way programmers have traditionally addressed this issue is to use the goto statement. The goto statement has been accused of many things including the demise of structured programming. Some would tell you the use of the goto is never warranted and that another logic construct must be used.
Sometimes the goto statement can be an elegant solution to the alternative of using nested if-then-else statements. Listing 22.2 shows how the goto statement can be used to simplify the handling of exceptional conditions. As you can see, all the cleanup logic is collected in one place. The same logic is used to deallocate resources regardless of whether the function succeeds or fails. Logic is simplified due to easy detection of which resources have been successfully allocated and hence must be freed up.
Listing 22.2 Example of Using goto Logic
void GotoExample()
{
// open an input file, and output file, and a log file
FILE * fpIn = NULL;
FILE * fpOut = NULL;
FILE * fpLog = NULL;
fpIn = fopen( c:\\temp\\in.txt, r ); if ( ! fpIn )
{
printf(error opening input file\n);
goto done;
}
fpOut = fopen( c:\\temp\\out.txt, w+ );
if ( ! fpOut )
{
printf( error opening output file\n);
goto done;
}
fpLog = fopen( c:\\temp\\log.out, a+ );
if ( ! fpOut )
{
printf( error opening log file\n);
goto done;
}
// do some processing on fpIn producing fpOut while
// logging to fpLog
done:
// close all the files
if ( fpIn )
fclose( fpIn );
if ( fpOut )
fclose( fpOut );
if (fpOut )
fclose( fpLog );
}
Exceptions are another way to handle the special processing that is required when logic strays from the straight and narrow. Exceptions allow a section of code to be executed under the umbrella of a try block. If things go wrong during the execution of the code, the processor can be vectored to a catch block. The catch block can perform orderly cleanup of resources and program state info as well as logging the cause of the exception such that the source of the problem can be quickly determined and remedied. Listing 22.3 is an example of using exceptions to handle errors. (In this example, resources are cleaned up outside the catch block.)
Listing 22.3 Example of Using Exception Processing
void ExceptionExample()
{
// open an input file, and output file, and a log file
FILE * fpIn = NULL;
FILE * fpOut = NULL;
FILE * fpLog = NULL;
try
{
fpIn = fopen( c:\\temp\\in.txt, r );
if ( ! fpIn ) throw Error opening c:\\temp\\in.txt;
fpOut = fopen( c:\\temp\\out.txt, w+ );
if ( ! fpOut ) throw Error opening c:\\temp\\out.txt;
fpLog = fopen( c:\\temp\\log.out, a+ );
if ( ! fpOut ) throw Error opening c:\\temp\\log.out;
// do some processing on fpIn producing fpOut while
// logging to fpLog
}
catch( char * pszCause )
{
printf(%s\n, pszCause);
}
// clean up
if ( fpIn )
fclose( fpIn );
if ( fpOut )
fclose( fpOut );
if (fpOut )
fclose( fpLog );
}
When programming in the MFC/C++ environment, a programmer should be cognizant of two major sources of exceptions: structured exceptions and C++ exceptions generated by MFC. Both of these sources of exceptions come along as part of the territory and, although you may choose to close your eyes and ignore them, they will probably occur at some point during the lifetime of your application and hence should at least be considered.
In addition to structured exceptions and MFC-generated exceptions, you can choose to derive your own exception classes based on MFCs CException class. This can provide an elegant method of passing out-of-the-ordinary conditions up the call stack.